
#include "LowLevelDisk.h"

using namespace std;

LowLevelDisk::LowLevelDisk()
{

}

void LowLevelDisk::setFileSys(FILE* file, SuperBlock* sb)
{
	_FILE_SYS = file;
	_superBlock = sb;
}


// gets an offset in FileSYS, and returns address from there
int LowLevelDisk::getAddrFromPointer(int addr)
{
	int ans;
	fseek(_FILE_SYS, addr,SEEK_SET); // goes to offset of inode address
	fread(&ans, sizeof(int),1,_FILE_SYS);

	return ans;
}

void LowLevelDisk::setFreeInodesAddressesBlock()
{
	/*------------------------------------------------------------/
	 *fill the list off free inodes with their addresses.     ----/
	 *-----------------------------------------------------------*/
	//points to the list of free inodes.
	fseek(_FILE_SYS,_superBlock->getFirstFreeInodeOffset(), SEEK_SET);
	int inodesOffset = _superBlock->getInodeArrayOffset() + sizeof(Inode);
	for(int i = 0; i<10*sizeof(Inode); i = i+sizeof(Inode))
	{
		int k = inodesOffset + i;
		fwrite(&k, sizeof(int), 1, _FILE_SYS);
	}
}

/*	removes next free iNode
 * 	if was last in block - updates blocks
 *
 * ** assuming the number of free Inodes > 0 **
 *	returns number of selected Inode
 * */
int LowLevelDisk::allocateInode()
{
	if(_superBlock->getFreeInodesNum() == 0)
	{
//		fwrite("no more free i-nodes\n",21,1,stderr );
		cerr<<"allocatInode:: no more free i-nodes"<<endl;
	}
	else
	{
		int pointerOffset = _superBlock->getFirstFreeInodeOffset();

		int InodeAddress = getAddrFromPointer(pointerOffset);

		//updating parameters
		_superBlock->decFreeInodesNum();
		_superBlock->addtoFirstFreeInodeOffset(sizeof(int)); //move to next pointer

		if (_superBlock->SBInodeIsEmpty()) //if pointer is on last entry (= pointer to next block)
		{
			iNodeBlockSwap();
			//_superBlock->setFirstFreeInodeOffset(sizeof(int));
		}

		cout<<"calling get inode number with offset: "<<InodeAddress<<endl;
		cout<<"pointer offset is: "<<pointerOffset<<endl;
		int freenum = _superBlock->getInodeNumber(InodeAddress);
		cout<<"setting inode: "<<freenum<<endl;
		setInodeType(freenum,3); //3= allocated, no type set yet
		cout<<"inode allocated number is "<< freenum<<endl;
		return freenum;
	}
	return -1;
}

// 1) puts second item in list in SB
// 2) adjusts last pointer to new empty block
void LowLevelDisk::iNodeBlockSwap()
{
//  1)
	cout<<"IN iNodeBlockSwap!!!!!"<<endl;
	int blocksize = getBlockSize();
	_superBlock->addtoFirstFreeInodeOffset(-blocksize+4); //returning to beginning of block in SB

	char* buffer = (char*)malloc (blocksize);
	size_t result;

	int firstInodeOffset = _superBlock->getFirstFreeInodeOffset();
	int secondNode = _superBlock->getFreeInodeListOffsetOutsideSB();
	//moving to 2nd node-block location
	fseek(_FILE_SYS ,secondNode , SEEK_SET);

	// copy the 2nd node into the buffer:
	result = fread(buffer,1,blocksize,_FILE_SYS);
	if (result != blocksize)
	{
		fputs ("Coudnt read all Inode BLock",stdout);
		exit (3);
	}
	//moving to location in SuperBlock
	fseek (_FILE_SYS ,firstInodeOffset , SEEK_SET);

	fwrite (buffer , blocksize ,1 , _FILE_SYS );

//  2)	adjusts last pointer to new empty block
	int lastInode = _superBlock->getLastInodeOffset();
	int ptrLoc = _superBlock->getBeginningOfBlock(lastInode) + blocksize - sizeof(int);
	fseek(_FILE_SYS,ptrLoc,SEEK_SET);
	int check;
	fread(&check,sizeof(int),1,_FILE_SYS);
	while (check != 0) // not in last block
	{
		fseek(_FILE_SYS,check+blocksize-4,SEEK_SET); // next-block-pointer location in last list-Node
		fread(&check,sizeof(int),1,_FILE_SYS);
	}
	fwrite(&secondNode, sizeof(int), 1, _FILE_SYS);


//	updating parameters

	int newSecondNode = secondNode + blocksize - sizeof(int);
	int newAddr;
	fseek(_FILE_SYS, newSecondNode, SEEK_SET);
	fread (&newAddr,sizeof(int),1,_FILE_SYS);
	_superBlock->setFreeInodeListOffsetOutsideSB(newAddr);

//	/updating last block
	int i = 0;
	fseek(_FILE_SYS, newSecondNode, SEEK_SET);
	fwrite(&i, sizeof(int), 1, _FILE_SYS);

}

void LowLevelDisk::freeInode(int inode)
{
	cout<<"free Inode"<<inode<<endl;
	if (inode > _superBlock->getINodeArraySize())
	{
//		fwrite("trying to free an inode that doesnt exist\n",42,1,stderr );
		cerr<<"trying to free inode"<<inode<<" that doesnt exist"<<endl;
	}
	else
	{
		Inode n = getInode(inode);
//		cout<<"(free Inode) printing iNode "<<inode<<endl;
//		printInode(n);
		if (getInode(inode).type == -1)
		{
//			fwrite("cannot free an unallocated iNode",32,1,stderr );
			cerr<<"trying to free inode"<<inode<<" that is already free"<<endl;
		}
		else
		{
			// –frees the specified i-node and also
			//frees all the data blocks pointed to by it (directly or indirectly).
			cout<<"freeing inode "<<inode<<endl;
			addToFreeInodeList(inode);
			freeAtachedBlocks(inode); //using freeDataBlock();
		}
	}
}


int LowLevelDisk::allocateDataBlock()
{
	if(_superBlock->getFreeBlocksNum() == 0)
	{

//		fwrite("no more free blocks",19,1,stderr );
		cerr<<"allocateDataBlock::no more free blocks"<<endl;

	}
	else
	{
		int pointerOffset = _superBlock->getFirstFreeBlockOffset();
		int InodeAddress = getAddrFromPointer(pointerOffset); //reads address from pointer pos

		//updating parameters
		_superBlock->decFreeBlocksNum();
		_superBlock->addtoFirstFreeBlockOffset(sizeof(int));

		if (_superBlock->SBBlockIsEmpty())
		{
			freeblocksBlockSwap();
		}

		int freenum = blockAddrToNum(InodeAddress);
		cout<<"alocated block number is "<< freenum<<endl;
		return freenum;
	}
	return -1;
}
/////////*************************************************************************************

void LowLevelDisk::freeblocksBlockSwap()
{
//  1)

	int blocksize = getBlockSize();
	_superBlock->addtoFirstFreeBlockOffset(-blocksize+4); //returning to beginning of block in SB

	char * buffer;
	size_t result;


	int firstBlockOffset = _superBlock->getFirstFreeBlockOffset();
	int secondNode = _superBlock->getFreeBlockListOffsetOutsideSB();
//	//moving to 2nd node-block location
	fseek(_FILE_SYS ,secondNode , SEEK_SET);

//	// copy the 2nd node into the buffer:
	result = fread (buffer,blocksize,1,_FILE_SYS);
	if (result != blocksize)
	{
		fputs ("Coudnt read all Inode BLock",stdout);
		exit (3);
	}
	//moving to location in SuperBlock
	fseek (_FILE_SYS ,firstBlockOffset , SEEK_SET);

	fwrite (buffer , blocksize ,1 , _FILE_SYS );

//  2)	adjusts last pointer to new empty block
	int lastBlock = _superBlock->getLastBlockOffset();
	int ptrLoc = _superBlock->getBeginningOfBlock(lastBlock) + blocksize - sizeof(int);
	fseek(_FILE_SYS,ptrLoc,SEEK_SET);
	int check;
	fread(&check,sizeof(int),1,_FILE_SYS);
	while (check != 0) // not in last block
	{
		fseek(_FILE_SYS,check+blocksize-4,SEEK_SET); // next-block-pointer location in last list-Node
		fread(&check,sizeof(int),1,_FILE_SYS);
	}
	fwrite(&secondNode, sizeof(int), 1, _FILE_SYS);



//	updating parameters

	int newSecondNode = secondNode + blocksize - sizeof(int);
	int newAddr;
	fseek(_FILE_SYS, newSecondNode, SEEK_SET);
	fread (&newAddr,sizeof(int),1,_FILE_SYS);
	_superBlock->setFreeBlockListOffsetOutsideSB(newAddr);

	//updating last block
	int i = 0;
	fseek(_FILE_SYS, newSecondNode, SEEK_SET);
	fwrite(&i, sizeof(int), 1, _FILE_SYS);

}


int LowLevelDisk::getEntriesPerBlock()
{
	return _superBlock->getEntriesPerBlock();
}

/////////******************===============================================================
void LowLevelDisk::freeDataBlock(int dblock)
{
	// – frees the specified block.
	addToFreeBlockList(dblock);

	//Use (and update) the maps in the super block in these allocate/free methods.
}


int LowLevelDisk::getInodeType(int inode)
{
	Inode n = getInode(inode);
	if (n.type == -1)
	{
//		fwrite("cant get type from free iNode",29,1,stderr );
		cerr<<"getInodeType:: cant get type from free inode"<<inode<<endl;
		return -1;
	}
	else
	{
		return (int)n.type;
	}
}


void LowLevelDisk::setInodeType(int inode, char filetype)
{
	// – set the type of the file
	//(0 - normal/ 1 - directory/ 2 - soft-link/ 3 - allocated but no type allocated yet).
	Inode n = getInode(inode);
	n.type= filetype;
	updateInode(inode, n);

	cout<<"setting inode number "<<inode<<" to be of type: "<<(int)filetype<<endl;
}


int LowLevelDisk::getDataBlock (int inode, int i)
{
	// – return the number of the i-th data block containing the data of
	// this file (for 0<=i<=9 this is just stored in the 10 pointers
	// in this inode, for i>9, you must retrieve it from the indirect
	// data block..).

	int blockAdd;
	int temp = _superBlock->getRootInodeNumber();
	temp = temp + inode*sizeof(Inode) + 2*sizeof(char) + sizeof(int); //points to the beginning of the blocks array.

	if( (i>=0) & (i<=9) )
	{
		temp =  temp + sizeof(int)*i;
	}
	else if(i>9)
	{	//points to the indirect block
		temp = temp + sizeof(int)*10;
		fseek(_FILE_SYS,temp, SEEK_SET);
		fread(&temp, sizeof(int), 1, _FILE_SYS);
		//temp holds the address of the indirect block.
		temp = temp + sizeof(int)*(i-10);
		//temp holds the address of the pointer
	}

	fseek(_FILE_SYS,temp, SEEK_SET);
	fread(&blockAdd, sizeof(int), 1, _FILE_SYS);
	//  now blockAdd holds the address of the wanted block.

	return blockAdd/getBlockSize();

}


void LowLevelDisk::setDataBlock (int inode, int i, int dblockNum )
{
	// – sets the i-th data block of this file to be dblockNum.
	int blockAdd = dblockNum*getBlockSize();
	int temp = _superBlock->getRootInodeNumber();
	temp = temp + inode*sizeof(Inode) + 2*sizeof(char) + sizeof(int); //points to the beginning of the blocks array.

	if( (i>=0) & (i<=9) )
	{
		temp =  temp + sizeof(int)*i;
	}
	else if(i>9)
	{	//points to the indirect block
		temp = temp + sizeof(int)*10;
		fseek(_FILE_SYS,temp, SEEK_SET);
		fread(&temp, sizeof(int), 1, _FILE_SYS);
		//temp holds the address of the indirect block.
		temp = temp + sizeof(int)*(i-10);
		//temp holds the address of the pointer
	}
	fseek(_FILE_SYS,temp, SEEK_SET);
	fwrite(&blockAdd, sizeof(int), 1, _FILE_SYS);
}


void LowLevelDisk::readBlock(int dblockNum, char* buf)
{
	//– read the entire content of the dblockNum's and copy it into buf.
	int blockSize = getBlockSize();
	int address = blockSize*dblockNum;

	fseek(_FILE_SYS, address, SEEK_SET);
	fread(buf, blockSize, 1, _FILE_SYS);

}


void LowLevelDisk::writeBlock(int dblockNum, char* newdata)
{
	// – overwrite the content of block with number dblockNum with the newdata block. Assume newdata's length is equal to dataBlockSize.
	int blockSize = getBlockSize();
	int address = blockSize*dblockNum;

	fseek(_FILE_SYS, address, SEEK_SET);
	fwrite(newdata, blockSize, 1, _FILE_SYS);
}



int LowLevelDisk::getFileSize(int inode)
{
	Inode n = getInode(inode);
	if (n.type == -1)
	{
		cerr<<"getFileSize:: cant get size from free inode"<<inode<<endl;
		return -1;
	}
	else
	{
		cout<<"inode file size is"<<n.fileSize<<endl;
		return n.fileSize;
	}
}


Inode LowLevelDisk::getInode(int inodeNum){
	Inode n;
	int inodeOff = _superBlock->getInodeArrayOffset() + inodeNum * sizeof(Inode);
	fseek(_FILE_SYS ,inodeOff, SEEK_SET);
	fread(&n,sizeof(Inode),1,_FILE_SYS);
	return n;
}

void LowLevelDisk::updateInode(int inode, Inode n)
{
	int inodeOff = _superBlock->getInodeArrayOffset() + inode * sizeof(Inode);
	fseek(_FILE_SYS ,inodeOff, SEEK_SET);
	fwrite(&n,sizeof(Inode),1,_FILE_SYS);
}

void LowLevelDisk::setFileSize(int inode, int newSize)
{
	Inode n = getInode(inode);
	n.fileSize = newSize;
	updateInode(inode, n);
}

void LowLevelDisk::incHardlinksNumber(int inode)
{
	Inode n = getInode(inode);
	n.hardLinksNumber++;
	updateInode(inode, n);
}

void LowLevelDisk::InitInode (int inode)
{
	Inode n = newInode();
	updateInode(inode, n);

}

Inode LowLevelDisk::newInode()
{
	Inode n;
	n.fileSize = 0;
	n.hardLinksNumber = 0;
	n.type = -1;
	n.indirect = 0;
	for (int i = 0; i < 10; ++i) {
		n.blocks[i]=0;
	}
	return n;
}
int LowLevelDisk::getBlockSize()
{
	return _superBlock->getBlockSize();
}

// adding a freed Inode back to list
void LowLevelDisk::addToFreeInodeList(int num)
{
	int a = _superBlock->getLastInodeOffset();
	int inodeOff = _superBlock->getInodeArrayOffset() + num*sizeof(Inode);
	fseek (_FILE_SYS, a, SEEK_SET);
	fwrite(&inodeOff,sizeof(int),1,_FILE_SYS);
	_superBlock->setLastInodeOffset(a+4);

	// if next entry is pointer to next list node
	if (isLastEntry(a+4)) // then need to move to next list node
	{
		int newAddr; //for adding next free Inode
		// no need to seek, because after write, we are on address of next block
		fread(&newAddr, sizeof(int), 1, _FILE_SYS);
		_superBlock->setLastInodeOffset(newAddr);
	}
	_superBlock->incFreeInodesNum();
	cout<<"free inodes After freeing = "<<_superBlock->getFreeInodesNum()<<endl;
}
// when inode is freed, free attached blocks
void LowLevelDisk::freeAtachedBlocks(int inodeNum)
{
	int inodeAdd = _superBlock->getInodeArrayOffset() + inodeNum * sizeof(Inode);
	int blockSize = getBlockSize();
	fseek(_FILE_SYS, inodeAdd, SEEK_SET);
	Inode n;
	fread(&n, sizeof(Inode), 1, _FILE_SYS);
	int numOfBlocks = ceil((double)n.fileSize/blockSize);

	// if indirect blocks are used
	if (numOfBlocks >= 10){
		int indirectBlocks = numOfBlocks - 10;
		int indirectAddr = blockNumToAddr(n.indirect);
		fseek(_FILE_SYS,indirectAddr,SEEK_SET); //moving to indirect block
		for (int i = 0; i < indirectBlocks; ++i) {
			int adr;
			fread(&adr,sizeof(int),1,_FILE_SYS);
			freeDataBlock(blockAddrToNum(adr));
		}
		freeDataBlock(blockAddrToNum(indirectAddr)); //releasing the indirect block
		numOfBlocks = 10;
	}

	//releasing direct blocks used
	for (int i = 0; i < numOfBlocks; ++i) {
		freeDataBlock(n.blocks[i]);
	}
}


void LowLevelDisk::addToFreeBlockList(int block)
{
	int a = _superBlock->getLastBlockOffset();
	int blockOff = block*getBlockSize();
	fseek (_FILE_SYS, a, SEEK_SET);
	fwrite(&blockOff,sizeof(int),1,_FILE_SYS);
	_superBlock->setLastBlockOffset(a+4);

	// if next entry is pointer to next list node
	if (isLastEntry(a+4)) // then need to move to next list node
	{
		int newAddr; //for adding next free Inode
		// no need to seek, because after write, we are on address of next block
		fread(&newAddr, sizeof(int), 1, _FILE_SYS);
		_superBlock->setLastBlockOffset(newAddr);
	}
	_superBlock->incFreeBlocksNum();
}

bool LowLevelDisk::isLastEntry(int addr)
{
	int beg = _superBlock->getBeginningOfBlock(addr);
	return addr - beg == getBlockSize()-4;

}

int LowLevelDisk::blockNumToAddr(int num)
{
	return getBlockSize()*num;
}

int LowLevelDisk::blockAddrToNum(int addr)
{
	return addr/getBlockSize();
}




int LowLevelDisk::getHardLinksNumber(int inode)
{
	Inode n = getInode(inode);
	if (n.type == -1)
	{
//		fwrite("cant get hard links number from free iNode",29,1,stderr );
		cerr<<"getHL:: cant get hard links number from free iNode"<<inode<<endl;
		return -1;
	}
	else
	{
		return (int)n.hardLinksNumber;
	}
}

void LowLevelDisk::setHardLinksNumber(int inode, int num)
{
	Inode n = getInode(inode);
	n.hardLinksNumber = num;
	updateInode(inode, n);
}

void LowLevelDisk::printInode(Inode n)
{
	cout<<"***Inode***"<<endl;
	cout<<"type= "<<(int)n.type <<endl;
	cout<<"filesize=  "<<n.fileSize <<endl;
	cout<<"HL=  "<<(int)n.hardLinksNumber <<endl;
	cout<<"indirec=  "<<n.indirect <<endl;
	cout<<"direct[3]=  "<<n.blocks[3]<<endl;

}
